package org.eggermont.hm.util;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

/**
 * Representation of an interval on the real line.
 * 
 * @author michiel
 * 
 */
public class Interval {

    /**
     * The lower boundary of the interval. Its value is always less or equal to
     * the {@link #upperBound}.
     */
    private final double lowerBound;

    /**
     * the upper boundary of the interval. Its value is always greater or equal
     * to the {@link #lowerBound}.
     */
    private final double upperBound;

    /**
     * Constructs an interval with the range [min(a,b), max(a,b)].
     * 
     * @param a
     *            a value for lower or upper boundary.
     * @param b
     *            a value for lower or upper boundary.
     */
    public Interval(final double a, final double b) {
        this.lowerBound = Math.min(a, b);
        this.upperBound = Math.max(a, b);
    }

    public double width() {
        return upperBound - lowerBound;
    }

    public double eval(double fraction) {
        return lowerBound + fraction * width();
    }

    public Interval union(final Interval other) {
        return new Interval(Math.min(lowerBound, other.lowerBound), Math.max(
                upperBound, other.upperBound));
    }

    public Interval[] subdivide(double fraction) {
        final double mid = lowerBound + fraction * width();
        return new Interval[] { new Interval(lowerBound, mid),
                new Interval(mid, upperBound) };
    }

    public double getLowerBound() {
        return lowerBound;
    }

    public double getUpperBound() {
        return upperBound;
    }

    /**
     * Returns the distance between a point x and this interval. If the point is
     * in the interval the distance returned is 0.
     * 
     * @param x
     *            a point on the real line.
     * @return distance between x and this interval.
     */
    public double distance(double x) {
        if (x > upperBound) {
            return x - upperBound;
        }
        if (x < lowerBound) {
            return lowerBound - x;
        }
        return 0.0;
    }

    /**
     * Returns the minimum distance between this interval and another one.
     * 
     * @param interval
     *            an other interval
     * @return minimum distance between intervals
     */
    public double distance(final Interval interval) {
        if (lowerBound > interval.lowerBound) {
            return interval.distance(this);
        } else {
            return distance(interval.lowerBound);
        }
    }

    @Override
    public final boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if ((o == null) || (getClass() != o.getClass())) {
            return false;
        }
        final Interval other = (Interval) o;
        return new EqualsBuilder().append(lowerBound, other.lowerBound).append(
                upperBound, other.upperBound).isEquals();
    }

    @Override
    public final int hashCode() {
        return new HashCodeBuilder(3, 17).append(lowerBound).append(upperBound)
                .toHashCode();
    }

    @Override
    public final String toString() {
        return "[" + lowerBound + ", " + upperBound + "]";
    }
}
